package main

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"net"
	"net/url"
	"os"
	"strconv"
	"strings"
)

var crlf = []byte{'\r', '\n'}

type HeaderWriter interface {
	WriteHeaderLine(value string) (n int, err error)
	WriteHeader(key, value string) (n int, err error)
}

type RequestVerboseWriter struct {
	mw        io.Writer
	stdPrefix string
}

func newRequestVerboseWriter(conn net.Conn) *RequestVerboseWriter {
	return &RequestVerboseWriter{
		mw:        io.MultiWriter(conn, os.Stdout),
		stdPrefix: ">>> ",
	}
}

func (rcw *RequestVerboseWriter) WriteHeaderLine(format string, args ...any) (n int, err error) {
	fmt.Fprint(os.Stdout, rcw.stdPrefix)
	line := fmt.Sprintf(format, args...)
	if line == "" {
		return rcw.mw.Write(crlf)
	}
	n, err = rcw.mw.Write([]byte(line))
	if t, e := rcw.mw.Write(crlf); err != nil {
		return n + t, e
	} else {
		n += t
	}
	return
}

func (rcw *RequestVerboseWriter) WriteHeader(key, value string) (n int, err error) {
	return rcw.WriteHeaderLine("%s: %s", key, value)
}

func doGetRequest(conn net.Conn, u *url.URL, done chan struct{}) {
	defer close(done)
	query := u.RawQuery
	if query != "" {
		query = "?" + query
	}
	path := u.Path
	if path == "" {
		path = "/"
	}
	wout("Writing Request: %s://%s%s%s", u.Scheme, u.Host, path, query)
	wsep("Request")
	rw := newRequestVerboseWriter(conn)
	rw.WriteHeaderLine("GET %s%s HTTP/1.1", path, query)
	rw.WriteHeader("Host", u.Host)
	rw.WriteHeader("User-Agent", "Http Request 0.1")
	rw.WriteHeader("Connection", "close")
	rw.WriteHeaderLine("")
	wsep("")
	wout("Request Write complete.")
}

func receiveResponse(conn net.Conn, done chan struct{}) {
	defer close(done)
	r := bufio.NewReader(conn)
	started := false
	chunked := false
	for {
		line, _, err := r.ReadLine()
		if !started {
			started = true
			wout("Response Received")
			wsep("Response")
		}
		if err == io.EOF {
			werr(errors.New("un expected EOF"), "read response failed")
			break
		}
		if err != nil {
			werr(err, "read response error")
		}
		fmt.Fprint(os.Stdout, "<<< ")
		os.Stdout.Write(line)
		fmt.Fprintln(os.Stdout)
		if len(line) == 0 {
			break
		}
		header := strings.ToLower(strings.TrimSpace(string(line)))
		if strings.HasPrefix(header, "transfer-encoding") && strings.HasSuffix(header, "chunked") {
			chunked = true
		}
	}
	if !chunked {
		io.Copy(os.Stdout, r)
		return
	}
	for {
		length, _, err := r.ReadLine()
		if err != nil {
			if err != io.EOF {
				werr(err, "read response body chunk size error")
			}
			break
		}
		size, err := strconv.ParseUint(string(length), 16, 64)
		if err != nil {
			werr(err, "parse response body chunk size error")
		}
		if size <= 0 {
			break
		}
		n := uint64(0)
		for n < size {
			chunk, more, err := r.ReadLine()
			if err != nil {
				if err != io.EOF {
					werr(err, "read response body chunk block error")
				}
				break
			}
			r, _ := os.Stdout.Write(chunk)
			n += uint64(r)
			if !more {
				break
			}
		}
	}
	fmt.Fprintln(os.Stdout)
}
